package rmiClient;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import interfaces.Client_ITF;
import interfaces.Serveur_ITF;

/**
 * @author SBAI Kévin and CARTIER Arthur
 * 
 */
public class Client_IMPL implements Client_ITF {

	/**
	 * @author SBAI Kévin and CARTIER Arthur
	 * 
	 *         This class represent one instance of a thread in charge of asking
	 *         a friend for an election
	 * 
	 */
	public class election implements Future<score>, Callable<score> {

		Client_ITF friendToAsk;
		String electionId;
		score result;
		boolean isDone;

		public election(Client_ITF friendToAsk, String electionId) {
			super();
			this.friendToAsk = friendToAsk;
			this.electionId = electionId;
		}

		public void elect() {
			try {
				this.result = this.friendToAsk.election(electionId);
			} catch (RemoteException e) {
				this.result = new score();
			}
		}

		@Override
		public score call() throws Exception {
			elect();
			return this.result;
		}

		@Override
		public boolean cancel(boolean mayInterruptIfRunning) {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean isCancelled() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean isDone() {
			return this.isDone;
		}

		@Override
		public score get() throws InterruptedException, ExecutionException {
			return this.result;
		}

		@Override
		public score get(long timeout, TimeUnit unit)
				throws InterruptedException, ExecutionException,
				TimeoutException {
			return this.result;
		}

	}

	ArrayList<Client_ITF> neighbors;
	ArrayList<String> electionsHistory;
	Serveur_ITF server;
	int id;
	int scoreLocal;

	/**
	 *
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	/**
	 * 
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Client_IMPL))
			return false;
		Client_IMPL other = (Client_IMPL) obj;
		if (id != other.id)
			return false;
		return true;
	}

	public Client_IMPL(Serveur_ITF server, int id, int score) {
		super();
		this.server = server;
		this.id = id;
		this.neighbors = new ArrayList<Client_ITF>();
		this.scoreLocal = score;
		this.electionsHistory = new ArrayList<String>();
	}

	public Serveur_ITF getServer() {
		return server;
	}

	public void setServer(Serveur_ITF server) {
		this.server = server;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public void setNeighbors(ArrayList<Client_ITF> neighbors) {
		this.neighbors = neighbors;
	}

	/**
	 * @see interfaces.Client_ITF#addNeighbor(interfaces.Client_ITF)
	 * @param newNeighbors
	 *            the neighbor to add
	 */
	@Override
	public void addNeighbor(Client_ITF newNeighbor) throws RemoteException {
		System.out.println("addNeighbor(Client_ITF newNeighbor) coté client");
		neighbors.add(newNeighbor);
	}

	/**
	 * @see interfaces.Client_ITF#removeNeighbor(interfaces.Client_ITF)
	 * @param neighbor
	 *            the neighbor to remove
	 */
	@Override
	public void removeNeighbor(Client_ITF neighbor) throws RemoteException {
		neighbors.remove(neighbor);
		if (neighbors.size() < 2) {
			ArrayList<Client_ITF> newNeighbors = server.getNeighbors(this);
			for (Client_ITF newNeighbor : newNeighbors) {
				if (!neighbors.contains(newNeighbor)) {
					neighbors.add(newNeighbor);
				}
			}
		}
	}

	@Override
	public ArrayList<Client_ITF> getNeighbors() throws RemoteException {
		return neighbors;
	}

	/**
	 * @see interfaces.Client_ITF#election(java.lang.String)
	 */
	@Override
	public score election(String p_electionId) throws RemoteException {
		score res = null;
		String electionId;
		if (p_electionId == null || p_electionId.isEmpty()) {
			// initiate the election
			electionId = "" + this.id + "-" + (new Date()).getTime();
		} else {
			electionId = p_electionId;
		}

		if (this.electionsHistory.size() > 0
				&& this.electionsHistory.contains(electionId)) {
			// election already computed return a fake score
			res = new score();
		} else {
			// proced to the election but first record the electionId !!!

			this.electionsHistory.add(electionId);

			ArrayList<Future<score>> friendsResults = new ArrayList<Future<score>>();
			ExecutorService executor = Executors.newCachedThreadPool();
			for (Client_ITF currentClient : this.neighbors) {
				Future<score> calculateur = new election(currentClient,
						electionId);
				friendsResults.add(executor
						.submit((Callable<score>) calculateur));
			}
			res = new score(this.scoreLocal, this);
			// TODO allow to check results in the same order we get them
			for (Future<score> currentResult : friendsResults) {
				try {
					if (res.compareTo(currentResult.get()) < 0) {
						res = currentResult.get();
					}
				} catch (InterruptedException e) {
					// if this thread has been stopped we just gona take an
					// other result
				} catch (ExecutionException e) {
					// if an error occurs there is probably an error on the
					// friend we are asking so we gona choose an other
				}
			}
		}
		return res;
	}

	public void display_neighbors() {
		int i = 1;

		System.out.println("Client " + this.id + " neighbors list : ");
		for (Client_ITF neighbor : this.neighbors) {
			System.out.println("\tNeighbor number : " + i + "\tid : "
					+ ((Client_IMPL) neighbor).getId());
			i++;
		}
		System.out.print("\n");
	}
}
